package org.budo.warehouse.logic.impl;

import java.net.InetSocketAddress;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.budo.canal.logposition.BudoLogPositionManager;
import org.budo.support.javax.sql.util.JdbcUtil;
import org.budo.support.lang.util.ListUtil;
import org.budo.support.lang.util.NumberUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.spring.util.SpringUtil;
import org.budo.warehouse.logic.api.IDataNodeLogic;
import org.budo.warehouse.logic.api.ILogPositionLogic;
import org.budo.warehouse.logic.entity.BinaryLogs;
import org.budo.warehouse.logic.entity.BinlogPosition;
import org.budo.warehouse.logic.entity.MasterStatus;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import com.alibaba.otter.canal.instance.core.CanalInstance;
import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
import com.alibaba.otter.canal.protocol.position.LogPosition;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Slf4j
@Component
public class LogPositionLogicImpl implements ILogPositionLogic {
    @Resource
    private ApplicationContext applicationContext;

    @Resource
    private IDataNodeLogic dataNodeLogic;

    @Override
    public List<BinlogPosition> listLogPosition() {
        List<BinlogPosition> list = new ArrayList<BinlogPosition>();

        List<String> canalInstanceNames = SpringUtil.getBeanNamesForType(this.applicationContext, CanalInstance.class);
        for (String canalInstanceName : canalInstanceNames) {
            CanalInstance canalInstance = (CanalInstance) SpringUtil.getBean(this.applicationContext, canalInstanceName);
            if (null == canalInstance) {
                log.error("#130 canalInstance=" + canalInstance + ", canalInstanceName=" + canalInstanceName);
                continue;
            }

            MysqlEventParser eventParser = (MysqlEventParser) canalInstance.getEventParser();
            AuthenticationInfo masterInfo = (AuthenticationInfo) ReflectUtil.getFieldValue(MysqlEventParser.class, "masterInfo", eventParser);

            MasterStatus masterStatus = this.masterStatus(masterInfo);
            Object slaveId = ReflectUtil.getFieldValue(MysqlEventParser.class, "slaveId", eventParser);

            InetSocketAddress masterAddress = masterInfo.getAddress();

            BinlogPosition binlogPosition = new BinlogPosition();

            Integer dataNodeId = dataNodeLogic.dataNodeIdByCanalDestinationName(canalInstance.getDestination());

            binlogPosition.setDataNodeId(dataNodeId) //
                    .setDestination(canalInstance.getDestination()) //
                    .setServerId(eventParser.getServerId()) //
                    .setSlaveId(NumberUtil.toLong(slaveId)) //
                    .setMasterFile(masterStatus.getFile()) //
                    .setMasterPosition(masterStatus.getPosition());

            // slavePosition
            CanalLogPositionManager logPositionManager = eventParser.getLogPositionManager();
            if (!logPositionManager.isStart()) {
                log.error("#144 logPositionManager not started, logPositionManager=" + logPositionManager + ", eventParser=" + eventParser //
                        + ", canalInstance=" + canalInstanceName + ", masterInfo=" + masterInfo + ", masterAddress=" + masterAddress);
                continue;
            }

            String destination = canalInstance.getDestination();
            LogPosition slavePosition = logPositionManager.getLatestIndexBy(destination);

            // add to list
            if (null != slavePosition && null != slavePosition.getPostion()) {
                binlogPosition.setSlaveJournalName(slavePosition.getPostion().getJournalName()) //
                        .setSlavePosition(slavePosition.getPostion().getPosition()) //
                        .setSlaveTimestamp(slavePosition.getPostion().getTimestamp());
            }

            list.add(binlogPosition);
        }

        return list;
    }

    @Override
    public MasterStatus masterStatus(AuthenticationInfo masterInfo) {
        String url = "jdbc:mysql://" + masterInfo.getAddress().getHostName() + ":" + masterInfo.getAddress().getPort();

        Connection connection = JdbcUtil.getConnection(url, masterInfo.getUsername(), masterInfo.getPassword());
        List<Map<String, Object>> list = JdbcUtil.executeQuery(connection, "SHOW MASTER STATUS");

        Map<String, Object> map = ListUtil.first(list);

        if (null == map) {
            log.error("#115 list=" + list + ", url=" + url + ", masterInfo=" + masterInfo);
            return null;
        }

        MasterStatus masterStatus = new MasterStatus() //
                .setFile(StringUtil.toString(map.get("File"))) //
                .setPosition(NumberUtil.toLong(map.get("Position")));

        log.info("#112 masterStatus=" + masterStatus + ", list=" + list + ", url=" + url + ", masterInfo=" + masterInfo);
        return masterStatus;
    }

    @Override
    public List<BinaryLogs> showBinaryLogs(String destination) {
        AuthenticationInfo masterInfo = this.findMasterInfoByDestination(destination);
        if (null == masterInfo) {
            log.error("#128 findMasterInfoByDestination = " + masterInfo + ", destination=" + destination);
            return null;
        }

        String url = "jdbc:mysql://" + masterInfo.getAddress().getHostName() + ":" + masterInfo.getAddress().getPort();

        Connection connection = JdbcUtil.getConnection(url, masterInfo.getUsername(), masterInfo.getPassword());
        List<Map<String, Object>> list = JdbcUtil.executeQuery(connection, "SHOW BINARY LOGS");

        if (null == list) {
            log.error("#128 SHOW BINARY LOGS = " + list + ", url=" + url + ", masterInfo=" + masterInfo + ", destination=" + destination);
            return null;
        }

        if (list.isEmpty()) {
            log.error("#133 SHOW BINARY LOGS = " + list + ", url=" + url + ", masterInfo=" + masterInfo + ", destination=" + destination);
            return null;
        }

        List<BinaryLogs> binaryLogList = new ArrayList<BinaryLogs>();
        for (Map<String, Object> map : list) {
            binaryLogList.add(new BinaryLogs() //
                    .setLogName(StringUtil.toString(map.get("Log_name"))) //
                    .setFileSize(NumberUtil.toLong(map.get("File_size"))));
        }

        log.info("#145 binaryLogList = " + binaryLogList + ", url=" + url + ", masterInfo=" + masterInfo + ", destination=" + destination);
        return binaryLogList;
    }

    @Override
    public Boolean deleteLogPosition(String destination) {
        CanalLogPositionManager logPositionManager = this.findLogPositionManagerByDestination(destination);

        if (null == logPositionManager || !(logPositionManager instanceof BudoLogPositionManager)) {
            log.error("#170 destination=" + destination + ", logPositionManager=" + logPositionManager);
            return false;
        }

        BudoLogPositionManager manager = (BudoLogPositionManager) logPositionManager;
        return manager.deleteLogPosition(destination);
    }

    private AuthenticationInfo findMasterInfoByDestination(String destination) {
        CanalInstance canalInstance = (CanalInstance) SpringUtil.getBean(this.applicationContext, destination);
        if (null == canalInstance) {
            log.error("#166 findMasterInfoByDestination return null, destination=" + destination);
            return null;
        }

        MysqlEventParser eventParser = (MysqlEventParser) canalInstance.getEventParser();
        AuthenticationInfo masterInfo = (AuthenticationInfo) ReflectUtil.getFieldValue(MysqlEventParser.class, "masterInfo", eventParser);

        return masterInfo;
    }

    private CanalLogPositionManager findLogPositionManagerByDestination(String destination) {
        CanalInstance canalInstance = this.findCanalInstanceByDestination(destination);
        if (null == canalInstance) {
            log.error("#179 findLogPositionManagerByDestination return null, destination=" + destination);
            return null;
        }

        MysqlEventParser eventParser = (MysqlEventParser) canalInstance.getEventParser();

        // slavePosition
        CanalLogPositionManager logPositionManager = eventParser.getLogPositionManager();
        return logPositionManager;
    }

    private CanalInstance findCanalInstanceByDestination(String destination) {
        List<String> canalInstanceNames = SpringUtil.getBeanNamesForType(this.applicationContext, CanalInstance.class);
        for (String canalInstanceName : canalInstanceNames) {
            CanalInstance canalInstance = (CanalInstance) SpringUtil.getBean(this.applicationContext, canalInstanceName);
            if (null == canalInstance) {
                log.error("#212 findCanalInstanceByDestination, canalInstance=" + canalInstance + ", canalInstanceName=" + canalInstanceName);
                continue;
            }

            if (StringUtil.equals(destination, canalInstance.getDestination())) {
                return canalInstance;
            }
        }

        log.error("#221 findCanalInstanceByDestination return null, destination=" + destination);
        return null;
    }
}